//! `GET /_matrix/client/*/capabilities`
//!
//! Get information about the server's supported feature set and other relevant capabilities
//! ([spec]).
//!
//! [spec]: https://spec.matrix.org/latest/client-server-api/#capabilities-negotiation

use std::{borrow::Cow, collections::BTreeMap};

use maplit::btreemap;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, from_value as from_json_value, to_value as to_json_value};

use crate::client::profile::ProfileFieldName;
use crate::{PrivOwnedStr, RoomVersionId, serde::StringEnum};

// /// `/v3/` ([spec])
// ///
// /// [spec]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3capabilities
// const METADATA: Metadata = metadata! {
//     method: GET,
//     rate_limited: true,
//     authentication: AccessToken,
//     history: {
//         1.0 => "/_matrix/client/r0/capabilities",
//         1.1 => "/_matrix/client/v3/capabilities",
//     }
// };

/// Response type for the `get_capabilities` endpoint.

#[derive(ToSchema, Serialize, Debug)]
pub struct CapabilitiesResBody {
    /// The capabilities the server supports
    pub capabilities: Capabilities,
}

impl CapabilitiesResBody {
    /// Creates a new `Response` with the given capabilities.
    pub fn new(capabilities: Capabilities) -> Self {
        Self { capabilities }
    }
}

/// Contains information about all the capabilities that the server supports.
#[derive(ToSchema, Clone, Debug, Default, Serialize, Deserialize)]
#[allow(deprecated)]
pub struct Capabilities {
    /// Capability to indicate if the user can change their password.
    #[serde(rename = "m.change_password", default)]
    pub change_password: ChangePasswordCapability,

    /// The room versions the server supports.
    #[serde(rename = "m.room_versions", default)]
    pub room_versions: RoomVersionsCapability,

    /// Capability to indicate if the user can change their display name.
    #[serde(rename = "m.set_display_name", default)]
    #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
    pub set_display_name: SetDisplayNameCapability,

    /// Capability to indicate if the user can change their avatar.
    #[serde(rename = "m.set_avatar_url", default)]
    #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
    pub set_avatar_url: SetAvatarUrlCapability,

    /// Capability to indicate if the user can change the third-party
    /// identifiers associated with their account.
    #[serde(rename = "m.3pid_changes", default)]
    pub thirdparty_id_changes: ThirdPartyIdChangesCapability,

    /// Capability to indicate if the user can set extended profile fields.
    #[serde(
        rename = "m.profile_fields",
        alias = "uk.tcpip.msc4133.profile_fields",
        skip_serializing_if = "Option::is_none"
    )]
    pub profile_fields: Option<ProfileFieldsCapability>,

    /// Any other custom capabilities that the server supports outside of the
    /// specification, labeled using the Java package naming convention and
    /// stored as arbitrary JSON values.
    #[serde(flatten)]
    #[salvo(schema(skip))]
    #[salvo(schema(value_type = Object, additional_properties = true))]
    pub custom_capabilities: BTreeMap<String, JsonValue>,
}

impl Capabilities {
    /// Creates empty `Capabilities`.
    pub fn new() -> Self {
        Default::default()
    }

    /// Returns the value of the given capability.
    ///
    /// Prefer to use the public fields of `Capabilities` where possible; this
    /// method is meant to be used for unsupported capabilities only.
    pub fn get(&self, capability: &str) -> Option<Cow<'_, JsonValue>> {
        fn serialize<T: Serialize>(cap: &T) -> JsonValue {
            to_json_value(cap).expect("capability serialization to succeed")
        }

        match capability {
            "m.change_password" => Some(Cow::Owned(serialize(&self.change_password))),
            "m.room_versions" => Some(Cow::Owned(serialize(&self.room_versions))),
            #[allow(deprecated)]
            "m.set_displayname" => Some(Cow::Owned(serialize(&self.set_display_name))),
            #[allow(deprecated)]
            "m.set_avatar_url" => Some(Cow::Owned(serialize(&self.set_avatar_url))),
            "m.3pid_changes" => Some(Cow::Owned(serialize(&self.thirdparty_id_changes))),
            _ => self.custom_capabilities.get(capability).map(Cow::Borrowed),
        }
    }

    /// Sets a capability to the given value.
    ///
    /// Prefer to use the public fields of `Capabilities` where possible; this
    /// method is meant to be used for unsupported capabilities only and
    /// does not allow setting arbitrary data for supported ones.
    pub fn set(&mut self, capability: &str, value: JsonValue) -> serde_json::Result<()> {
        match capability {
            "m.change_password" => self.change_password = from_json_value(value)?,
            "m.room_versions" => self.room_versions = from_json_value(value)?,
            #[allow(deprecated)]
            "m.set_displayname" => self.set_display_name = from_json_value(value)?,
            #[allow(deprecated)]
            "m.set_avatar_url" => self.set_avatar_url = from_json_value(value)?,
            "m.3pid_changes" => self.thirdparty_id_changes = from_json_value(value)?,
            _ => {
                self.custom_capabilities
                    .insert(capability.to_owned(), value);
            }
        }

        Ok(())
    }
}

/// Information about the m.change_password capability
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct ChangePasswordCapability {
    /// `true` if the user can change their password, `false` otherwise.
    pub enabled: bool,
}

impl ChangePasswordCapability {
    /// Creates a new `ChangePasswordCapability` with the given enabled flag.
    pub fn new(enabled: bool) -> Self {
        Self { enabled }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        self.enabled
    }
}

impl Default for ChangePasswordCapability {
    fn default() -> Self {
        Self { enabled: true }
    }
}

/// Information about the m.room_versions capability
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct RoomVersionsCapability {
    /// The default room version the server is using for new rooms.
    pub default: RoomVersionId,

    /// A detailed description of the room versions the server supports.
    pub available: BTreeMap<RoomVersionId, RoomVersionStability>,
}

impl RoomVersionsCapability {
    /// Creates a new `RoomVersionsCapability` with the given default room
    /// version ID and room version descriptions.
    pub fn new(
        default: RoomVersionId,
        available: BTreeMap<RoomVersionId, RoomVersionStability>,
    ) -> Self {
        Self { default, available }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        self.default == RoomVersionId::V1
            && self.available.len() == 1
            && self
                .available
                .get(&RoomVersionId::V1)
                .map(|stability| *stability == RoomVersionStability::Stable)
                .unwrap_or(false)
    }
}

impl Default for RoomVersionsCapability {
    fn default() -> Self {
        Self {
            default: RoomVersionId::V1,
            available: btreemap! { RoomVersionId::V1 => RoomVersionStability::Stable },
        }
    }
}

/// The stability of a room version.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(ToSchema, Clone, StringEnum)]
#[palpo_enum(rename_all = "lowercase")]
#[non_exhaustive]
pub enum RoomVersionStability {
    /// Support for the given version is stable.
    Stable,

    /// Support for the given version is unstable.
    Unstable,

    #[doc(hidden)]
    #[salvo(schema(skip))]
    _Custom(PrivOwnedStr),
}

/// Information about the `m.set_display_name` capability
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
#[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
pub struct SetDisplayNameCapability {
    /// `true` if the user can change their display name, `false` otherwise.
    pub enabled: bool,
}

#[allow(deprecated)]
impl SetDisplayNameCapability {
    /// Creates a new `SetDisplayNameCapability` with the given enabled flag.
    pub fn new(enabled: bool) -> Self {
        Self { enabled }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        self.enabled
    }
}

#[allow(deprecated)]
impl Default for SetDisplayNameCapability {
    fn default() -> Self {
        Self { enabled: true }
    }
}

/// Information about the `m.set_avatar_url` capability
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
#[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
pub struct SetAvatarUrlCapability {
    /// `true` if the user can change their avatar, `false` otherwise.
    pub enabled: bool,
}

#[allow(deprecated)]
impl SetAvatarUrlCapability {
    /// Creates a new `SetAvatarUrlCapability` with the given enabled flag.
    pub fn new(enabled: bool) -> Self {
        Self { enabled }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        self.enabled
    }
}

#[allow(deprecated)]
impl Default for SetAvatarUrlCapability {
    fn default() -> Self {
        Self { enabled: true }
    }
}

/// Information about the `m.3pid_changes` capability
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct ThirdPartyIdChangesCapability {
    /// `true` if the user can change the third-party identifiers associated
    /// with their account, `false` otherwise.
    pub enabled: bool,
}

impl ThirdPartyIdChangesCapability {
    /// Creates a new `ThirdPartyIdChangesCapability` with the given enabled
    /// flag.
    pub fn new(enabled: bool) -> Self {
        Self { enabled }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        self.enabled
    }
}
impl Default for ThirdPartyIdChangesCapability {
    fn default() -> Self {
        Self { enabled: true }
    }
}

/// Information about the `m.get_login_token` capability.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct GetLoginTokenCapability {
    /// Whether the user can request a login token.
    pub enabled: bool,
}

impl GetLoginTokenCapability {
    /// Creates a new `GetLoginTokenCapability` with the given enabled flag.
    pub fn new(enabled: bool) -> Self {
        Self { enabled }
    }

    /// Returns whether all fields have their default value.
    pub fn is_default(&self) -> bool {
        !self.enabled
    }
}

/// Information about the `m.profile_fields` capability.
#[derive(ToSchema, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProfileFieldsCapability {
    /// Whether the user can set extended profile fields.
    pub enabled: bool,

    /// The fields that can be set by the user.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub allowed: Option<Vec<ProfileFieldName>>,

    /// The fields that cannot be set by the user.
    ///
    /// This list is ignored if `allowed` is provided.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub disallowed: Option<Vec<ProfileFieldName>>,
}

impl ProfileFieldsCapability {
    /// Creates a new `ProfileFieldsCapability` with the given enabled flag.
    pub fn new(enabled: bool) -> Self {
        Self {
            enabled,
            allowed: None,
            disallowed: None,
        }
    }

    /// Whether the server advertises that the field with the given name can be set.
    pub fn can_set_field(&self, field: &ProfileFieldName) -> bool {
        if !self.enabled {
            return false;
        }

        if let Some(allowed) = &self.allowed {
            allowed.contains(field)
        } else if let Some(disallowed) = &self.disallowed {
            !disallowed.contains(field)
        } else {
            // The default is that any field is allowed.
            true
        }
    }
}
